package client;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;



/**************************************************************************************************************
 * Listens for connection on an open port and creates a client handler thread for each connecting client
 * @author Kim Persson
 *
 **************************************************************************************************************/
public class ServerClient extends Thread{
	
	private String path;
	private ServerSocket ss;
	private boolean disconnected;
	private ClientCommunication com;
	/***
	 * Default constructor
	 * @param path path to shared files folder
	 * @throws IOException failed to create server socket
	 */
	public ServerClient(String path, ClientCommunication com) throws IOException
	{
		this.com = com;
		this.path = path;
		disconnected = false;
		ss = new ServerSocket(0);
	}
	

	/**
	 * Gets the ip address of computer
	 * @return String representing ip address
	 * @throws UnknownHostException failed to get ip
	 */
	public String getServerAddress() throws UnknownHostException
	{
		return InetAddress.getLocalHost().getHostAddress();
	}
	
	
	/**
	 * Gets the port of which ServerSocket is listening
	 * @return port number
	 */
	public int getServerPort()
	{
		return ss.getLocalPort();
	}
	
	/**
	 * Closes server socket
	 * @throws IOException failed to close socket
	 */
	public void disconnect() throws IOException
	{
		disconnected = true;
		ss.close();
	}
	
	/**
	 * Listens for connection on an open port and creates a client handler thread for each connecting client
	 */
	public void run()
	{
		try {
			System.out.println("ServerSocket started on " + getServerAddress() + ":" + getServerPort());
			
			while(true)
			{
				new ClientHandler(ss.accept()).start();
			}  
		} catch (IOException e) {
			if(!disconnected)
				System.out.println("ERROR: Couldn't accept incoming connection " + e.getMessage());
			else
				return;
		}
	}
	
	/*******************************************************************************************************
	 * Handles client download request and transfers the file over socket
	 * @author Kim Persson
	 *******************************************************************************************************/
	private class ClientHandler extends Thread
	{
		private Socket s;
		/**
		 * Default constructor
		 * @param s Socket connection to handle
		 */
		public ClientHandler(Socket s)
		{
			this.s = s;
		}
		
		/**
		 *  Handles client download request and transfers the file over socket
		 */
		public void run()
		{
			
			try {
				BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
				String req = br.readLine();
				
				String[] split = req.split("#");
				
				if(split[0].equals("search"))
				{
					String[] keywords = getKeywords(split);
					try {
						int ttl = Integer.parseInt(split[1]);
						PrintWriter pw = new PrintWriter(s.getOutputStream());
						ArrayList<String> results;
						String resStr;
						if (ttl > 0)
							results = com.search(keywords,ttl);
						else
							results = com.searchLocalFiles(keywords);
			
						
						resStr = encodeResults(results);
						pw.println(resStr);
						pw.flush();
						pw.close();
						
					} catch (InterruptedException e) {
						System.out.println("Error search interrupted " + e.getMessage());
					} catch (NumberFormatException e)
					{
						System.out.println("Error reading Time To Live " + e.getMessage());
					}
				} else if(split[0].equals("request"))
				{
					File file = new File(path, secureFileName(split[1]));
					if (file.exists())
					{
						DataInputStream dis = new DataInputStream(new FileInputStream(file));
						BufferedInputStream bis = new BufferedInputStream(dis); 
						OutputStream os = s.getOutputStream();

						int count;
						byte[] buffer = new byte[8192];
						while ((count = bis.read(buffer)) > 0)
						{
						  os.write(buffer, 0, count);
						}
						
						dis.close();
						bis.close();
						os.close();
						
					} 
				}
				
			} catch (IOException e) {
				System.out.println("ERROR: Couldn't send file " + e.getMessage());
			}
		}
		
		/**
		 * Removes / and \ to ensure that client can't navigate outside of shared folder
		 * @param name filname to secure
		 * @return secured filename without / and \
		 */
		private String secureFileName(String name)
		{
			return name.replace("\\", "").replace("/", "");
		}
		
		/**
		 * Removes first 2 indexes from Array to isolate keywords from protocol
		 * @param message Split protocol message
		 * @return keywords in protocol message
		 */
		private String[] getKeywords(String[] message)
		{
			String[] tmp = new String[message.length - 2];
			
			for(int i = 2; i < message.length; i++)
				tmp[i-2] = message[i];
			
			return tmp;
		}
		
		/**
		 * Encodes the ArrayList with search results into a protocol correct String
		 * @param searchResults results from search
		 * @return protocol correct formatted response String with results
		 */
		private String encodeResults(ArrayList<String> searchResults)
		{
			String response = "results#";
			for(String result : searchResults)
				response += result + "#";
			
			response = response.substring(0, response.lastIndexOf('#'));
			return response;
		}
	}
}
